package com.icoolkj.ms.upload.service.impl;

import cn.hutool.core.util.StrUtil;
import com.icoolkj.ms.common.core.enums.ResponseEnum;
import com.icoolkj.ms.common.core.exception.IcoolkjMSException;
import com.icoolkj.ms.common.core.utils.file.FileTypeUtils;
import com.icoolkj.ms.common.core.utils.file.MimeTypeUtils;
import com.icoolkj.ms.common.core.utils.uuid.IdWorker;
import com.icoolkj.ms.common.security.AuthUserContext;
import com.icoolkj.ms.upload.config.MinioConfig;
import com.icoolkj.ms.upload.mapper.FileInfoMapper;
import com.icoolkj.ms.upload.model.FileInfo;
import com.icoolkj.ms.upload.service.FileService;
import com.icoolkj.ms.upload.util.MinioUtil;
import com.icoolkj.ms.upload.vo.FileInfoVO;
import io.minio.ObjectWriteResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 文件服务实现类
 * 
 * @author icoolkj
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {

    @Autowired
    private MinioConfig minioConfig;

    @Autowired
    private MinioUtil minioUtil;

    @Autowired
    private FileInfoMapper fileInfoMapper;
    
    /**
     * 允许上传的文件类型
     */
    @Value("${file.allowed-types:image/jpeg,image/png,image/gif,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet}")
    private String allowedTypes;
    
    /**
     * 最大文件大小（默认10MB）
     */
    @Value("${file.max-size:10485760}")
    private long maxFileSize;

    /**
     * 上传文件
     *
     * @param file         文件
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @return 文件信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileInfoVO upload(MultipartFile file, String businessType, String businessId) {
        try {
            // 验证文件
            validateFile(file);

            // 获取文件名
            String originalFilename = file.getOriginalFilename();
            // 获取文件后缀
            String extension = FileTypeUtils.getExtension(originalFilename);
            // 生成文件名
            String fileName = cn.hutool.core.date.DateUtil.format(cn.hutool.core.date.DateUtil.date(), "yyyy/MM/dd") + "/" + cn.hutool.core.util.IdUtil.fastSimpleUUID() + "." + extension;
            // 获取文件大小
            long size = file.getSize();
            // 获取文件类型
            String contentType = file.getContentType();

            // 上传文件到MinIO
            ObjectWriteResponse response = minioUtil.uploadFile(minioConfig.getBucketName(), fileName, file.getInputStream(), contentType);
            if (response == null) {
                throw new IcoolkjMSException(ResponseEnum.UPLOAD_FILE_FAIL);
            }

            // 构建文件信息
            FileInfo fileInfo = new FileInfo();
            fileInfo.setFileId(IdWorker.nextId());
            fileInfo.setFileName(fileName);
            fileInfo.setOriginalFileName(originalFilename);
            fileInfo.setExtension(extension);
            fileInfo.setSize(size);
            fileInfo.setContentType(contentType);
            fileInfo.setBucketName(minioConfig.getBucketName());
            fileInfo.setFilePath(fileName);
            fileInfo.setUrl(minioUtil.getFileUrl(minioConfig.getBucketName(), fileName));
            fileInfo.setUploaderId(AuthUserContext.get() != null ? AuthUserContext.get().getUserId() : null);
            fileInfo.setUploaderName(AuthUserContext.get() != null ? AuthUserContext.get().getUid().toString() : "anonymous");
            fileInfo.setBusinessType(businessType);
            fileInfo.setBusinessId(businessId);
            fileInfo.setStatus(0); // 0: 临时文件，1: 正式文件
            fileInfo.setDelFlag("0"); // 0: 正常，2: 已删除
            fileInfo.setCreateBy(AuthUserContext.get() != null ? AuthUserContext.get().getUid().toString() : "anonymous");
            fileInfo.setUpdateBy(AuthUserContext.get() != null ? AuthUserContext.get().getUid().toString() : "anonymous");

            // 保存文件信息
            fileInfoMapper.insert(fileInfo);

            // 转换为VO
            FileInfoVO fileInfoVO = new FileInfoVO();
            BeanUtils.copyProperties(fileInfo, fileInfoVO);

            return fileInfoVO;
        } catch (IOException e) {
            log.error("文件上传失败", e);
            throw new IcoolkjMSException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 验证文件
     *
     * @param file 文件
     */
    private void validateFile(MultipartFile file) {
        // 检查文件是否为空
        if (file == null || file.isEmpty()) {
            throw new IcoolkjMSException("上传文件不能为空");
        }

        // 检查文件大小
        if (file.getSize() > maxFileSize) {
            throw new IcoolkjMSException("文件大小不能超过" + (maxFileSize / 1024 / 1024) + "MB");
        }

        // 获取文件名和内容类型
        String fileName = file.getOriginalFilename();
        String contentType = file.getContentType();
        
        // 获取文件后缀
        String extension = FileTypeUtils.getExtension(fileName);
        
        // 检查文件类型（同时验证扩展名和MIME类型）
        if (!isAllowedFile(extension, contentType)) {
            throw new IcoolkjMSException(ResponseEnum.UPLOAD_FILE_TYPE_NOT_SUPPORT);
        }
    }

    /**
     * 检查文件是否允许上传
     *
     * @param extension   文件扩展名
     * @param contentType 文件MIME类型
     * @return 是否允许
     */
    private boolean isAllowedFile(String extension, String contentType) {
        // 检查扩展名是否在允许列表中
        boolean isExtensionAllowed = isAllowedExtension(extension);
        
        // 检查MIME类型是否在允许列表中
        boolean isContentTypeAllowed = isAllowedContentType(contentType);
        
        // 两者都满足才返回true
        return isExtensionAllowed && isContentTypeAllowed;
    }

    /**
     * 检查文件扩展名是否允许
     *
     * @param extension 文件后缀
     * @return 是否允许
     */
    private boolean isAllowedExtension(String extension) {
        if (StrUtil.isEmpty(extension)) {
            return false;
        }
        
        // 允许的文件类型列表
        List<String> allowedExtensions = new ArrayList<>();
        allowedExtensions.addAll(Arrays.asList(MimeTypeUtils.IMAGE_EXTENSION));
        allowedExtensions.addAll(Arrays.asList(MimeTypeUtils.DOCUMENT_EXTENSION));
        allowedExtensions.addAll(Arrays.asList(MimeTypeUtils.MEDIA_EXTENSION));
        allowedExtensions.addAll(Arrays.asList(MimeTypeUtils.COMPRESS_EXTENSION));

        return allowedExtensions.contains(extension.toLowerCase());
    }
    
    /**
     * 检查文件MIME类型是否允许
     *
     * @param contentType 文件MIME类型
     * @return 是否允许
     */
    private boolean isAllowedContentType(String contentType) {
        if (StrUtil.isEmpty(contentType)) {
            return false;
        }
        
        // 将配置的允许类型字符串拆分为列表
        String[] allowedTypeArray = allowedTypes.split(",");
        List<String> allowedTypeList = Arrays.asList(allowedTypeArray);
        
        return allowedTypeList.contains(contentType.toLowerCase());
    }

    /**
     * 批量上传文件
     *
     * @param files        文件列表
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @return 文件信息列表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileInfoVO> batchUpload(List<MultipartFile> files, String businessType, String businessId) {
        return files.stream()
                .map(file -> upload(file, businessType, businessId))
                .collect(Collectors.toList());
    }

    /**
     * 上传文件并设置状态
     *
     * @param file         文件
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @param isPermanent  是否为永久文件
     * @return 文件信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public FileInfoVO uploadWithStatus(MultipartFile file, String businessType, String businessId, boolean isPermanent) {
        FileInfoVO fileInfoVO = upload(file, businessType, businessId);
        if (isPermanent && fileInfoVO != null) {
            updateStatus(fileInfoVO.getFileId(), 1);
        }
        return fileInfoVO;
    }

    /**
     * 批量上传文件并设置状态
     *
     * @param files        文件列表
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @param isPermanent  是否为永久文件
     * @return 文件信息列表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<FileInfoVO> batchUploadWithStatus(List<MultipartFile> files, String businessType, String businessId, boolean isPermanent) {
        List<FileInfoVO> fileInfoVOs = batchUpload(files, businessType, businessId);
        if (isPermanent && !fileInfoVOs.isEmpty()) {
            Long[] fileIds = fileInfoVOs.stream().map(FileInfoVO::getFileId).toArray(Long[]::new);
            batchUpdateStatus(fileIds, 1);
        }
        return fileInfoVOs;
    }

    /**
     * 删除文件
     *
     * @param fileId 文件ID
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(Long fileId) {
        // 查询文件信息
        FileInfo fileInfo = fileInfoMapper.selectById(fileId);
        if (fileInfo == null) {
            throw new IcoolkjMSException("文件不存在");
        }

        // 逻辑删除文件信息
        fileInfo.setDelFlag("2");
        fileInfo.setUpdateBy(AuthUserContext.get() != null ? AuthUserContext.get().getUid().toString() : "anonymous");
        int rows = fileInfoMapper.update(fileInfo);
        
        return rows > 0;
    }

    /**
     * 批量删除文件
     *
     * @param fileIds 文件ID数组
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDelete(Long[] fileIds) {
        // 批量逻辑删除文件信息
        int rows = fileInfoMapper.deleteByIds(fileIds);
        return rows > 0;
    }

    /**
     * 物理删除文件（管理员操作）
     *
     * @param fileId 文件ID
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deletePhysical(Long fileId) {
        // 查询文件信息
        FileInfo fileInfo = fileInfoMapper.selectById(fileId);
        if (fileInfo == null) {
            throw new IcoolkjMSException("文件不存在");
        }

        // 物理删除文件信息
        int rows = fileInfoMapper.deleteByIdPhysical(fileId);
        if (rows > 0) {
            // 物理删除MinIO中的文件
            minioUtil.removeObject(fileInfo.getBucketName(), fileInfo.getFilePath());
            return true;
        }
        return false;
    }

    /**
     * 批量物理删除文件（管理员操作）
     *
     * @param fileIds 文件ID数组
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeletePhysical(Long[] fileIds) {
        // 查询文件信息
        for (Long fileId : fileIds) {
            FileInfo fileInfo = fileInfoMapper.selectById(fileId);
            if (fileInfo != null) {
                // 物理删除MinIO中的文件
                minioUtil.removeObject(fileInfo.getBucketName(), fileInfo.getFilePath());
            }
        }
        
        // 批量物理删除文件信息
        int rows = fileInfoMapper.deleteByIdsPhysical(fileIds);
        return rows > 0;
    }

    /**
     * 根据ID获取文件信息
     *
     * @param fileId 文件ID
     * @return 文件信息
     */
    @Override
    public FileInfoVO getInfo(Long fileId) {
        FileInfo fileInfo = fileInfoMapper.selectById(fileId);
        if (fileInfo == null) {
            return null;
        }

        FileInfoVO fileInfoVO = new FileInfoVO();
        BeanUtils.copyProperties(fileInfo, fileInfoVO);
        return fileInfoVO;
    }

    /**
     * 查询文件列表
     *
     * @param fileInfo 文件信息
     * @return 文件列表
     */
    @Override
    public List<FileInfoVO> getList(FileInfo fileInfo) {
        List<FileInfo> list = fileInfoMapper.selectList(fileInfo);
        return list.stream().map(info -> {
            FileInfoVO vo = new FileInfoVO();
            BeanUtils.copyProperties(info, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 根据业务类型和业务ID查询文件列表
     *
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @return 文件列表
     */
    @Override
    public List<FileInfoVO> getListByBusiness(String businessType, String businessId) {
        if (StrUtil.isEmpty(businessType) || StrUtil.isEmpty(businessId)) {
            return new ArrayList<>();
        }

        List<FileInfo> list = fileInfoMapper.selectByBusiness(businessType, businessId);
        return list.stream().map(info -> {
            FileInfoVO vo = new FileInfoVO();
            BeanUtils.copyProperties(info, vo);
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 更新文件状态
     *
     * @param fileId 文件ID
     * @param status 状态（0: 临时文件，1: 正式文件）
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStatus(Long fileId, Integer status) {
        FileInfo fileInfo = new FileInfo();
        fileInfo.setFileId(fileId);
        fileInfo.setStatus(status);
        fileInfo.setUpdateBy(AuthUserContext.get() != null ? AuthUserContext.get().getUid().toString() : "anonymous");
        return fileInfoMapper.update(fileInfo) > 0;
    }

    /**
     * 批量更新文件状态
     *
     * @param fileIds 文件ID数组
     * @param status  状态（0: 临时文件，1: 正式文件）
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateStatus(Long[] fileIds, Integer status) {
        boolean success = true;
        for (Long fileId : fileIds) {
            if (!updateStatus(fileId, status)) {
                success = false;
            }
        }
        return success;
    }
    
    /**
     * 根据业务类型和业务ID更新文件状态
     *
     * @param businessType 业务类型
     * @param businessId   业务ID
     * @param status       状态（0: 临时文件，1: 正式文件）
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateStatusByBusiness(String businessType, String businessId, Integer status) {
        if (StrUtil.isEmpty(businessType) || StrUtil.isEmpty(businessId)) {
            return false;
        }
        
        List<FileInfo> list = fileInfoMapper.selectByBusiness(businessType, businessId);
        if (list.isEmpty()) {
            return true;
        }
        
        boolean success = true;
        for (FileInfo fileInfo : list) {
            if (!updateStatus(fileInfo.getFileId(), status)) {
                success = false;
            }
        }
        return success;
    }
} 